/*
 * CHttpServer.cpp
 *
 *  Created on: 2016-7-25
 *      Author: terry
 */

#include "CHttpServer.h"
#include "TStringUtil.h"
#include "TStringCast.h"
#include <iostream>
#include <fstream>
#include "TFileUtil.h"
#include "CLog.h"
#include "HttpUtil.h"
#include "ClockTime.h"


#ifdef WIN32
    #include "WinUtf8Encoder.h"
	#define	PATH_SEPERATOR	'\\'
#else
	#define	PATH_SEPERATOR	'/'
#endif //


#define USER_DIGEST_FILE "user.htpasswd"
#define HTTP_DOMAIN		 "HttpServer"

static const int POLL_INTERVAL = 10;


class CHttpTimerTask : public HttpTimerTask
{
public:
	CHttpTimerTask()
		:m_time()
		, m_cycle()
	{
		m_time = getTime();
	}

	virtual bool open(int seconds, HttpServer::Task task)
	{
		m_cycle = seconds;
		m_task = task;

		return true;
	}

	virtual void close()
	{
		m_cycle = 0;
		m_task = HttpServer::Task();
	}

	virtual int poll()
	{
		if (m_cycle <= 0)
		{
			return POLL_INTERVAL;
		}

		uint64_t curTime = getTime();
		int duration = (curTime - m_time);
		if (duration >= m_cycle)
		{
			m_time = curTime;

			if (m_task)
			{
				m_task();
			}

			return std::min(POLL_INTERVAL, m_cycle);
		}
		return m_cycle - duration;
	}

	uint64_t getTime()
	{
		return comn::ClockTime::getTime() / 1000;
	}

	uint64_t	m_time;
	int			m_cycle;
	HttpServer::Task	m_task;

};


class MongooseHttpRequest : public HttpRequest
{
public:
	MongooseHttpRequest()
        :server()
        ,connection()
        ,message()
        ,method()
        ,uri()
        ,query()
		,body()
		,flags()
    {
    }

    virtual HttpServer* getServer()
    {
        return server;
    }

    virtual HttpConnection* getConnection()
    {
        return connection;
    }

    virtual std::string getToken()
    {
        return token;
    }

    virtual std::string getMethod()
    {
        return std::string(method.p, method.len);
    }

    virtual bool isMethod(const char* method)
    {
        mg_str str = mg_mk_str(method);
        int ret = mg_strcmp(this->method, str);
        return ret == 0;
    }

    virtual std::string getUrl()
    {
        return std::string(uri.p, uri.len);
    }

    virtual std::string getQuery()
    {
        return std::string(query.p, query.len);
    }

    //virtual IntString getBody() =0;

    virtual const char* getBody(size_t& len)
    {
        len = body.len;
        return body.p;
    }

    virtual bool getHeader(const char* name, std::string& value)
    {
		bool got = false;
		struct mg_str* str = mg_get_http_header(message, name);
		if (str)
		{
			value.assign(str->p, str->len);
			got = true;
		}
		return got;
    }

    virtual bool getParam(const char* name, std::string& value)
    {
        bool got = findUriParam(name, value);
        if (!got)
        {
           got = findQueryParam(name, value);
        }
        return got;
    }

	bool findUriParam(const char* name, std::string& value)
	{
	    bool got = false;
	    StringMap::const_iterator it = urlParams.find(name);
	    if (it != urlParams.end())
	    {
	        value = it->second;
	        got = true;
	    }
	    return got;
	}

	bool findQueryParam(const char* name, std::string& value)
	{
	    bool found = false;
	    char text[1024] = {0};
	    int ret = mg_get_http_var(&query, name, text, sizeof(text));
	    if (ret > 0)
	    {
	        value = text;
	        found = true;
	    }
	    return found;
	}

	unsigned char getFlags()
	{
		return flags;
	}

public:
    HttpServer* server;
    mg_connection* connection;
    http_message* message;
    StringMap   params;
    std::string token;

    mg_str method;
    mg_str uri;
    mg_str query;
    mg_str body;
	unsigned char flags;

};


class MongooseHttpUploadRequest : public MongooseHttpRequest
{
public:
	virtual std::string getFileName()
	{
		return filename;
	}

	virtual std::string getFilePath()
	{
		return filepath;
	}

	std::string filename;
	std::string filepath;
};







static void ev_null_handler(struct mg_connection *, int ev, void *)
{
	// pass
}

static bool mg_startswith(const struct mg_str& src, const struct mg_str& sub)
{
	if (src.p == nullptr)
	{
		return false;
	}

	if (src.len < sub.len)
	{
		return false;
	}

	for (size_t i = 0; i < sub.len; i++)
	{
		if (src.p[i] != sub.p[i])
		{
			return false;
		}
	}
	return true;
}

static bool is_websocket(const struct mg_connection *nc)
{
	return (nc->flags & MG_F_IS_WEBSOCKET) != 0;
}


void CHttpServer::ev_handler(struct mg_connection *nc, int ev, void *ev_data)
{
	CHttpServer* pThis = (CHttpServer*)nc->mgr->user_data;
	if (pThis == NULL)
	{
		pThis = (CHttpServer*)nc->user_data;
	}

	pThis->apiHandler(nc, ev, ev_data);
}

struct file_writer_data 
{
  FILE *fp;
  size_t bytes_written;
  std::string filepath;
  std::string origin;
  std::string filename;
  std::string uri;
  std::string query;

  file_writer_data():
	  fp(),
	  bytes_written()
  {
  }
};

static void handle_upload(struct mg_connection *nc, int ev, void *ev_data)
{
	CHttpServer* pThis = (CHttpServer*)nc->mgr->user_data;
	pThis->handleUpload(nc, ev, ev_data);

}

static void handle_direntry(struct mg_connection *nc, int ev, void *ev_data)
{
	CHttpServer* pThis = (CHttpServer*)nc->mgr->user_data;
	pThis->handleDirEntry(nc, ev, ev_data);
}


struct ValueName
{
    int value;
    const char* name;
};

static ValueName s_codeNames[] =
{
    {200, "OK"},
	{301, "Moved Permanently"},
	{308, "Permanent Redirect"},
    {400, "Bad Request"},
    {401, "Unauthorized"},
    {402, "Payment Required"},
    {403, "Forbidden   "},
    {404, "Not Found   "},
    {405, "Method Not Allowed"},
    {406, "Not Acceptable"},
    {407, "Proxy Authentication Required"},
    {408, "Request Timeout"},
    {410, "Gone        "},
    {411, "Length Required"},
    {412, "Precondition Failed"},
    {413, "Request Entity Too Large"},
    {414, "Request-URI Too Long"},
    {415, "Unsupported Media Type"},
    {451, "Invalid parameter"},
    {452, "Illegal Conference Identifier"},
    {453, "Not Enough Bandwidth"},
    {454, "Session Not Found"},
    {455, "Method Not Valid In This State"},
    {456, "Header Field Not Valid"},
    {457, "Invalid Range   PLAY"},
    {458, "Parameter Is Read-Only"},
    {459, "Aggregate Operation Not Allowed"},
    {460, "Only Aggregate Operation Allowed"},
    {461, "Unsupported Transport"},
    {462, "Destination Unreachable"},
    {500, "Internal Server Error"},
    {501, "Not Implemented"},
    {502, "Bad Gateway "},
    {503, "Service Unavailable"},
    {504, "Gateway Timeout"},
    {505, "RTSP Version Not Supported"},
    {551, "Option not support"},
};

const char* CHttpServer::getCodeName(int code)
{
    for (size_t i = 0; i < sizeof(s_codeNames)/sizeof(s_codeNames[0]); i ++)
    {
        if (code == s_codeNames[i].value)
        {
            return s_codeNames[i].name;
        }
    }
    return "";
}


CHttpServer::CHttpServer():
	m_handler(),
	m_mgr(),
	m_opts(),
	m_listenConnection(),
	m_sslPort(),
	m_sslConnection(),
	m_polling(),
	m_timerTask(new CHttpTimerTask())
{
	mg_mgr_init(&m_mgr, this);

	m_webRoot = ".";
	m_opts.document_root = m_webRoot.c_str();

	m_userFile = "";
	m_opts.auth_domain = HTTP_DOMAIN;
	m_opts.global_auth_file = m_userFile.c_str();
	m_opts.extra_headers = "Access-Control-Allow-Origin: *";
	
	m_uploadDir = ".";
	m_uploadUrl = "/upload";

	setupUser();
}

CHttpServer::~CHttpServer()
{
    mg_mgr_free(&m_mgr);

	if (m_timerTask)
	{
		m_timerTask->close();
		delete m_timerTask;
	}
}

bool CHttpServer::setWebRoot(const char* dirPath)
{
	if (!dirPath || strlen(dirPath) <= 0)
	{
		return false;
	}

	m_webRoot = dirPath;

	m_opts.document_root = m_webRoot.c_str();

	return true;
}

bool CHttpServer::setUploadDir(const char* url, const char* dirPath, UploadHandler handler)
{
	if (!dirPath || strlen(dirPath) <= 0)
	{
		return false;
	}

	m_uploadUrl = url;
	m_uploadDir = dirPath;
	m_uploadHandler = handler;

	return true;
}

bool CHttpServer::setUserFile(const char* filePath)
{
	if (!filePath || strlen(filePath) <= 0)
	{
		m_userFile.clear();
		m_opts.global_auth_file = NULL;
	}
	else
	{
		m_userFile = filePath;
		m_opts.global_auth_file = m_userFile.c_str();
	}

	return true;
}

bool CHttpServer::addDir(const char* url, const char* dirPath, HttpAuth auth)
{
	if (strlen(url) == 0)
	{
		return false;
	}

	if (strlen(dirPath) == 0)
	{
		return false;
	}

	DirEntry* pEntry = findDirEntry(url);
	if (pEntry)
	{
		pEntry->dirPath = dirPath;
		pEntry->auth = auth;
		pEntry->opts.document_root = pEntry->dirPath.c_str();
	}
	else
	{
		DirEntry entry;
	    entry.dirPath = dirPath;
	    entry.url = url;
	    entry.opts.document_root = entry.dirPath.c_str();
		entry.opts.extra_headers = m_opts.extra_headers;

		entry.auth = auth;

	    m_entryMap[url] = entry;
	}

	return true;
}

bool CHttpServer::start(const char* ip, int port, HttpHandler* handler)
{
	if (port < 0 || !handler)
	{
		return false;
	}

	m_handler = handler;

	std::string bindUrl = comn::StringUtil::format("%s:%d", ip, port);

	m_listenConnection = mg_bind(&m_mgr, bindUrl.c_str(), ev_handler);
	if (!m_listenConnection)
	{
		return false;
	}

	m_listenConnection->user_data = this;
	//mg_enable_multithreading(m_listenConnection);

	mg_set_protocol_http_websocket(m_listenConnection);

	mg_register_http_endpoint(m_listenConnection, m_uploadUrl.c_str(), handle_upload);


	DirEntryMap::iterator it = m_entryMap.begin();
	for (; it != m_entryMap.end(); ++ it)
	{
		mg_register_http_endpoint(m_listenConnection, it->first.c_str(), handle_direntry);
	}

	return comn::Thread::start();
}

void CHttpServer::stop()
{
	if (isRunning())
	{
		comn::Thread::stop();
	}

	clearAsyncRecord();

	m_connections.clear();
}

bool CHttpServer::isStarted()
{
	return comn::Thread::isRunning();
}

void CHttpServer::handleHttpEvent(struct mg_connection *nc, int ev, struct http_message *hm)
{
	if (HttpUtil::isOption(hm->method))
	{
		sendJsonResponse(nc, 200, NULL, "");
		return;
	}

	mg_str vfile_prefix = mg_mk_str_n(m_vfUrl.c_str(), m_vfUrl.size());

	std::string prefix;
	if (findPrefix(hm->uri, prefix))
	{
	    handleApi(nc, hm, hm->uri);
	}
	else if (vfile_prefix.len > 0 && mg_startswith(hm->uri, vfile_prefix))
	{
		handleFileApi(nc, hm, hm->uri);
	}
	else
	{
		mg_serve_http(nc, hm, m_opts);
	}
}


int CHttpServer::run()
{
	while (!m_canExit)
	{
		int seconds = m_timerTask->poll();

	    timedwait(1000 * seconds);

	    AsyncRecord record;
	    while (fetchAsyncRecord(record) && !m_canExit)
	    {
	        handleAsyncRecord(record);
	    }
	}
	return 0;
}

void CHttpServer::doStop()
{
	int ctrl = 0;
	mg_broadcast(&m_mgr, ev_null_handler, &ctrl, sizeof(ctrl));

	mg_wakeup(&m_mgr, 300);
}


void CHttpServer::postEvent()
{
    if (comn::Thread::getCurrentThreadId() == getThreadID())
    {
        return;
    }

    if (m_canExit)
    {
        if (m_polling)
        {
            mg_wakeup(&m_mgr, 100);
        }
    }
    else
    {
        if (m_polling)
        {
            int ctrl = 0;
            mg_broadcast(&m_mgr, ev_null_handler, &ctrl, sizeof(ctrl));
        }
    }
}

bool CHttpServer::timedwait(int ms)
{
    if (ms < 0)
    {
        ms = 1000 * 60;
    }

    if (m_canExit)
    {
        ms = 0;
    }

    m_polling = true;
    int ret = mg_mgr_poll(&m_mgr, ms);
    m_polling = false;

    return (ret > 0);
}

void CHttpServer::handleApi(struct mg_connection* nc, struct http_message *hm, mg_str& uri)
{
    HttpResult result;

	if (m_handle)
	{
	    MongooseHttpRequest request;
		request.server = this;
		request.connection = nc;
		request.message = hm;
		request.method = hm->method;
		request.uri = uri;
		request.query = hm->query_string;
		request.body = hm->body;


		int ret = m_handler->handle(request, result);
        if (ret == ASYNC_RESULT)
		{
			return;
		}
        else if (ret != 0)
        {
            result.code = convertStatusCode(ret);
        }
	}
	else
	{
	    result.code = 404;
	}

	sendApiResponse(nc, result);
}


void CHttpServer::sendApiResponse(mg_connection *nc, const HttpResult& result)
{
    sendResponse(nc, result.code, nullptr, "*", result.mime, result.content);
}

void CHttpServer::sendJsonResponse(mg_connection *nc, int code, const char *reason, const std::string& json)
{
    sendResponse(nc, code, reason, "*", "application/json", json);
}

void CHttpServer::sendJsonResponse(mg_connection *nc, int code, const char *reason, const char* json)
{
    sendResponse(nc, code, reason, "*", "application/json", json);
}


void CHttpServer::sendResponse(mg_connection *nc, int code, const char *reason,
	const std::string& origin,
	const std::string& mime,
	const std::string& content)
{
	if (reason == NULL)
	{
		reason = getCodeName(code);
	}

	mg_printf(nc, "HTTP/1.1 %d %s\r\n"
		"Cache-Control: no-cache\r\n"
		"Content-Type: %s\r\n"
		"Access-Control-Allow-Origin: %s\r\n"
		"Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Token\r\n"
		"Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT\r\n"
		//"Access-Control-Allow-Credentials: true\r\n"
		"Content-Length: %d\r\n\r\n%s",
		code, reason,
		mime.c_str(),
		origin.c_str(),
		(int)content.size(), content.c_str());
}

void CHttpServer::sendResponse(mg_connection *nc, int code, const char *reason,
	const std::string& origin,
	const std::string& mime,
	const char* content)
{
	if (reason == NULL)
	{
		reason = getCodeName(code);
	}

	int size = strlen(content);

	mg_printf(nc, "HTTP/1.1 %d %s\r\n"
		"Cache-Control: no-cache\r\n"
		"Content-Type: %s\r\n"
		"Access-Control-Allow-Origin: %s\r\n"
		"Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Token\r\n"
		"Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT\r\n"
		//"Access-Control-Allow-Credentials: true\r\n"
		"Content-Length: %d\r\n\r\n%s",
		code, reason,
		mime.c_str(),
		origin.c_str(),
		size, content);
}

void CHttpServer::handleFileApi(struct mg_connection* nc, struct http_message* hm, mg_str& uri)
{
	int code = 404;
	
	std::string mime = "application/octet-stream";
	std::string headers;

	HttpResult result;

	if (m_vfHandler)
	{
		MongooseHttpRequest request;
		request.server = this;
		request.connection = nc;
		request.message = hm;
		request.method = hm->method;
		request.uri = uri;
		request.query = hm->query_string;
		request.body = hm->body;

		int ret = m_vfHandler(request, result);
		if (ret == 0)
		{
			code = 200;

			if (result.headers.size() > 0)
			{
				std::ostringstream ss;
				for (StringMap::const_iterator it = result.headers.begin(); it != result.headers.end(); ++it)
				{
					ss << it->first;
					ss << ':' << ' ';
					ss << it->second;
					ss << "\r\n";
				}

				headers = ss.str();
			}
		}
		else if (ret < 100)
		{
			code = ret + 500;
		}
		else if (ret < 0)
		{
			code = 500;
		}

		if (result.mime.size() > 0)
		{
			mime = result.mime;
		}
	}
	else
	{
		//
	}

	const char* reason = getCodeName(code);
	mg_printf(nc, "HTTP/1.1 %d %s\r\n"
		"Cache-Control: no-cache\r\n"
		"Content-Type: %s\r\n"
		"Access-Control-Allow-Origin: *\r\n"
		"%s"
		"Content-Length: %d\r\n\r\n",
		code, reason,
		mime.c_str(),
		headers.c_str(),
		(int)result.content.size());

	if (result.content.size() > 0)
	{
		mg_send(nc, result.content.c_str(), result.content.size());
	}
}

void CHttpServer::handleUpload(struct mg_connection *nc, int ev, void *ev_data)
{
	struct file_writer_data * fileData  = (struct file_writer_data *) nc->user_data;
	struct mg_http_multipart_part *mp = (struct mg_http_multipart_part*)ev_data;

    if (ev == MG_EV_HTTP_PART_BEGIN)
    {
    	if (fileData)
    	{
            fileData->filename = makeUploadName(mp->file_name);
			fileData->filepath = makeUploadPath(fileData->uri, fileData->query, fileData->filename);
			fileData->fp = fopen(fileData->filepath.c_str(), "wb");
			fileData->bytes_written = 0;

            if (fileData->fp == NULL)
            {
            	mg_printf(nc, 
                        "HTTP/1.1 500 Failed to open a file\r\n"
						"Access-Control-Allow-Origin: %s\r\n"
						"Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Token\r\n"
						"Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT\r\n"
						//"Access-Control-Allow-Credentials: true\r\n"
                        "Content-Length: 0\r\n\r\n",
						fileData->origin.c_str());
            	nc->flags |= MG_F_SEND_AND_CLOSE;
            }
    	}
    }
	else if (ev == MG_EV_HTTP_PART_DATA)
	{
		if (fileData && fileData->fp)
		{
			if (fwrite(mp->data.p, 1, mp->data.len, fileData->fp) != mp->data.len)
			{
				mg_printf(nc, 
					"HTTP/1.1 500 Failed to write to a file\r\n"
					"Access-Control-Allow-Origin: %s\r\n"
					"Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Token\r\n"
					"Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT\r\n"
					//"Access-Control-Allow-Credentials: true\r\n"
					"Content-Length: 0\r\n\r\n",
					fileData->origin.c_str());
				nc->flags |= MG_F_SEND_AND_CLOSE;
				return;
			}
			fileData->bytes_written += mp->data.len;
		}
    }
    else if (ev == MG_EV_HTTP_PART_END)
    {
		if (fileData)
		{
            long size = 0;
            if (fileData->fp)
            {
                size = ftell(fileData->fp);
            }

            if (fileData->fp)
            {
    			fclose(fileData->fp);
            }

    		std::string filepath(fileData->filepath);
            std::string filename(fileData->filename);

			HttpResult result;

            int rc = 0;
            std::string json;
    		if (m_uploadHandler)
    		{
				MongooseHttpUploadRequest request;
				request.server = this;
				request.connection = nc;
				request.uri = mg_mk_str_n(fileData->uri.c_str(), fileData->uri.length());
				request.query = mg_mk_str_n(fileData->query.c_str(), fileData->query.length());
				request.filename = fileData->filename;
				request.filepath = fileData->filepath;

    			rc = m_uploadHandler(request, result);
    		}
            else
            {
                rc = ENOSYS;
            }

            sendApiResponse(nc, result);

            nc->flags |= MG_F_SEND_AND_CLOSE;

            delete fileData;
            nc->user_data = NULL;
		}
    }
    else if (ev == MG_EV_HTTP_MULTIPART_REQUEST)
    {
		struct http_message *hm = (struct http_message *) ev_data;
		std::string origin("*");
		struct mg_str *origin_hdr = mg_get_http_header(hm, "Origin");
		if (origin_hdr)
		{
			origin.assign(origin_hdr->p, origin_hdr->len);
		}

		fileData = new file_writer_data();
		fileData->origin = origin;
        fileData->uri.assign(hm->uri.p, hm->uri.len);
        fileData->query.assign(hm->query_string.p, hm->query_string.len);

		nc->user_data = fileData;
    }
	else if (ev == MG_EV_HTTP_REQUEST)
	{
		struct http_message *hm = (struct http_message *) ev_data;
		if (mg_vcmp(&hm->method, "OPTIONS") == 0)
		{
			
		}
		else
		{
			//
		}

		std::string origin("*");
		struct mg_str *origin_hdr = mg_get_http_header(hm, "Origin");
		if (origin_hdr)
		{
			origin.assign(origin_hdr->p, origin_hdr->len);
		}

		sendResponse(nc, 200, NULL, origin, "application/octet-stream", std::string());
	}
	
}

std::string CHttpServer::makeUploadName(const char* filename)
{
#ifdef WIN32
    // convert from utf-8 to ansi
	WinUtf8Encoder utf8Encoder;
    return utf8Encoder.utf8ToGbk(filename);
#else
    return filename;
#endif
}

std::string	CHttpServer::makeUploadPath(const std::string& uri, const std::string& query, const std::string& filename)
{
	std::string filedir = m_uploadDir;
	findUploadDir(uri, filedir);

	std::string fullpath = filedir;
	fullpath += PATH_SEPERATOR;

	fullpath += filename;
	return fullpath;
}

bool CHttpServer::setupUser()
{
	FILE* f = fopen(m_userFile.c_str(), "rb");
	if (f)
	{
		fclose(f);
		return true;
	}

	return resetUser("admin", "admin123");
}

std::string CHttpServer::makeUser(const std::string& name, const std::string& realm, const std::string& passwd)
{
	char buf[33] = {0};
	std::string src = name + ":" + realm + ":" + passwd;
    
    cs_md5_ctx ctx = cs_md5_ctx();
    cs_md5_init(&ctx);
    cs_md5_update(&ctx, (const unsigned char*)src.c_str(), src.size());
    cs_md5_final((unsigned char*)buf, &ctx);
    
	return buf;
}

bool CHttpServer::resetUser(const std::string& name, const std::string& passwd)
{
	bool done = false;
	FILE* f = fopen(m_userFile.c_str(), "wb");
	if (f)
	{
		std::string md5 = makeUser(name, HTTP_DOMAIN, passwd);
		std::string line(name);
		line += ":";
		line += HTTP_DOMAIN;
		line += ":";
		line += md5;
		fwrite(line.c_str(), 1, line.size(), f);
		fclose(f);
		done = true;
	}
	return done;
}

bool CHttpServer::findDirEntry(const char* url, DirEntry& entry)
{
	bool found = false;
	DirEntryMap::const_iterator it = m_entryMap.find(url);
	if (it != m_entryMap.end())
	{
		entry = it->second;
		found = true;
	}
	return found;
}

bool CHttpServer::findDirEntry(struct mg_str& url, DirEntry& entry)
{
	bool found = false;
	DirEntryMap::const_iterator it = m_entryMap.begin();
	for (; it != m_entryMap.end(); ++ it)
	{
		mg_str prefix = mg_mk_str(it->first.c_str());
		if (mg_startswith(url, prefix))
		{
			entry = it->second;
			found = true;
		}
	}
	return found;
}

CHttpServer::DirEntry* CHttpServer::findDirEntry(const char* url)
{
	DirEntryMap::iterator it = m_entryMap.find(url);
	if (it != m_entryMap.end())
	{
		return &(it->second);
	}
	return nullptr;
}

void CHttpServer::handleDirEntry(struct mg_connection *nc, int ev, void *ev_data)
{
	if (ev == MG_EV_HTTP_PART_DATA)
	{
		return;
	}

	struct http_message *hm = (struct http_message *)ev_data;
	if (!hm)
	{
		return;
	}

	DirEntry entry;
	if (findDirEntry(hm->uri, entry))
	{
		entry.opts.document_root = entry.dirPath.c_str();
		hm->uri.p = hm->uri.p + entry.url.size();
		hm->uri.len = hm->uri.len - entry.url.size();

		if (entry.auth)
		{
			HttpResult result;

			MongooseHttpRequest request;
			request.server = this;
			request.connection = nc;
			request.message = hm;
			request.method = hm->method;
			request.uri = hm->uri;
			request.query = hm->query_string;
			request.body = hm->body;

			int ret = entry.auth(request, result);
			if (ret == 0)
			{
				mg_serve_http(nc, hm, entry.opts);
			}
			else
			{
				if (result.code == 200)
				{
					result.code = 401;
				}

				sendApiResponse(nc, result);
			}
		}
		else
		{
			mg_serve_http(nc, hm, entry.opts);
		}
	}
	else
	{
		mg_printf(nc, "%s",
				"HTTP/1.1 404 Not found\r\n"
		        "Content-Length: 0\r\n\r\n");
	}
}

bool CHttpServer::addApi(const char* prefix)
{
	if ((prefix == NULL) || (strlen(prefix) == 0))
	{
		return false;
	}

	m_prefixMap[prefix] = prefix;

	return true;
}


bool CHttpServer::findPrefix(const struct mg_str& url, std::string& prefix)
{
	bool found = false;
	PrefixMap::const_iterator it = m_prefixMap.begin();
	for (; it != m_prefixMap.end(); ++it)
	{
		struct mg_str api_prefix = mg_mk_str(it->first.c_str());
		if (mg_startswith(url, api_prefix))
		{
			prefix = it->first;
			found = true;
			break;
		}
	}
	return found;
}


bool CHttpServer::fetchAsyncRecord(AsyncRecord& record)
{
	bool got = false;
	std::lock_guard< std::mutex > lock(m_mutex);
	if (m_asyncRecords.size() > 0)
	{
	    record = m_asyncRecords.front();
	    m_asyncRecords.pop_front();
	    got = true;
	}
	return got;
}

bool CHttpServer::putAsyncRecord(AsyncRecord& record)
{
    {
        std::lock_guard< std::mutex > lock(m_mutex);
        m_asyncRecords.push_back(record);
    }

    signalAsyncResult();
    return true;
}

void CHttpServer::signalAsyncResult()
{
	postEvent();
}


bool CHttpServer::sendResponse(HttpConnection* conn, const HttpResult& result)
{
    if (!conn)
    {
        return false;
    }

    AsyncRecord record;
    record.connection = (mg_connection *)conn;
    record.result = result;

    putAsyncRecord(record);

    return true;
}

bool CHttpServer::send(HttpConnection* conn, const void* data, size_t len)
{
    if (!conn)
    {
        return false;
    }

    AsyncRecord record;
    record.connection = (mg_connection*)conn;
    record.result.content.assign((const char*)data, len);
    record.result.code = -1;

    putAsyncRecord(record);

    return true;
}


void CHttpServer::handleAsyncRecord(AsyncRecord& record)
{
	if (record.connection)
	{
		if (is_websocket(record.connection))
		{
			mg_send_websocket_frame(record.connection, record.dataType, record.result.content.c_str(), record.result.content.size());
		}
		else
		{
			if (!m_connections.exist(record.connection))
			{
				return;
			}

			if (record.result.code == -1)
			{
				mg_send(record.connection, record.result.content.c_str(), record.result.content.length());
			}
			else
			{
				sendApiResponse(record.connection, record.result);
			}
		}
	}
	else
	{
		for (mg_connection* c = mg_next(&m_mgr, NULL); c != NULL; c = mg_next(&m_mgr, c)) 
		{
			if (is_websocket(c))
			{
				mg_send_websocket_frame(c, record.dataType, record.result.content.c_str(), record.result.content.size());
			}
		}
	}
}


int CHttpServer::convertStatusCode(int rc)
{
    int code = rc;
    if (rc == 0)
    {
        code = 200;
    }
    else if (rc < 200 || rc > 600)
    {
        code = 400;
    }
    return code;
}

void CHttpServer::clearAsyncRecord(mg_connection* connection)
{
    std::lock_guard< std::mutex > lock(m_mutex);

    auto pred = [=] (const AsyncRecord& record) {
        return (record.connection == connection);
    };
    AsyncRecordDeque::iterator it = std::remove_if(m_asyncRecords.begin(), m_asyncRecords.end(), pred);
	if (it != m_asyncRecords.end())
	{
		m_asyncRecords.erase(it);
	}
}

void CHttpServer::clearAsyncRecord()
{
    std::lock_guard< std::mutex > lock(m_mutex);
    m_asyncRecords.clear();
}

void CHttpServer::apiHandler(struct mg_connection *nc, int ev, void *ev_data)
{
	if (ev == MG_EV_POLL)
	{
		//
	}
	else if (ev == MG_EV_HTTP_REQUEST)
	{
		m_connections.put(nc, nc);

		struct http_message *hm = (struct http_message *) ev_data;
		handleHttpEvent(nc, ev, hm);
	}
	else if (ev == MG_EV_HTTP_MULTIPART_REQUEST)
	{
		struct http_message *hm = (struct http_message *) ev_data;
		handleHttpEvent(nc, ev, hm);

		m_connections.put(nc, nc);
	}
	else if (ev == MG_EV_TIMER)
	{
		//
	}
	else if (ev == MG_EV_SEND)
	{
		//
	}
	else if (ev == MG_EV_WEBSOCKET_HANDSHAKE_REQUEST)
	{
		struct http_message *hm = (struct http_message *) ev_data;
		mg_str ws_prefix = mg_mk_str_n(m_wsUrl.c_str(), m_wsUrl.size());

		if (mg_startswith(hm->uri, ws_prefix))
		{
			/// accept
			if (m_wsAuth)
			{
				HttpResult result;

				MongooseHttpRequest request;
				request.server = this;
				request.connection = nc;
				request.message = hm;
				request.method = hm->method;
				request.uri = hm->uri;
				request.query = hm->query_string;
				request.body = hm->body;

				int ret = m_wsAuth(request, result);
				if (ret != 0)
				{
					nc->flags |= MG_F_CLOSE_IMMEDIATELY;
				}
			}
		}
		else
		{
			/// reject
			nc->flags |= MG_F_CLOSE_IMMEDIATELY;
		}
	}
	else if (ev == MG_EV_CONNECT)
	{
	    
	}
	else if (ev == MG_EV_CLOSE)
	{
		handleClose(nc);
	}
	else if (ev == MG_EV_WEBSOCKET_FRAME)
	{
		struct websocket_message* wm = (struct websocket_message*)ev_data;
		if (m_wsHandler)
		{
			HttpResult result;

			MongooseHttpRequest request;
			request.server = this;
			request.connection = nc;
			request.body = mg_mk_str_n((const char*)wm->data, wm->size);
			request.flags = (wm->flags & 0x03);

			int ret = m_wsHandler(request, result);
			if (ret == 0 || ret == 200)
			{
				if (result.content.size() > 0)
				{
					mg_send_websocket_frame(nc, request.flags, result.content.c_str(), result.content.size());
				}
			}
			else if (ret == ASYNC_RESULT)
			{
				//
			}
			else
			{
				nc->flags |= MG_F_CLOSE_IMMEDIATELY;
			}
		}
	}
}

void CHttpServer::handleClose(struct mg_connection *nc)
{
    clearAsyncRecord(nc);
}

bool CHttpServer::isXml(const std::string& str)
{
	bool got = false;
	if (str.find_first_of('<') == 0)
	{
		size_t endPos = str.find_last_of('>');
		if (endPos == (str.size() - 1))
		{
			got = true;
		}
		else if (endPos == (str.size() - 2))
		{
			if ((str.back() == '\r') || (str.back() == '\n'))
			{
				got = true;
			}
		}
		else if (endPos == (str.size() - 3))
		{
			std::string back = str.substr(str.size() - 2);
			if (back == "\r\n" || back == "\n\r")
			{
				got = true;
			}
		}
	}
		
	return got;
}

bool CHttpServer::enableSsl(int port, const char* caPath, const char* certPath, const char* keyPath)
{
#if MG_ENABLE_SSL

	m_sslPort = comn::StringCast::toString(port);
	
	mg_bind_opts bind_opts;
	const char *err;

	memset(&bind_opts, 0, sizeof(bind_opts));

	if (caPath != nullptr && strlen(caPath) > 0)
	{
		bind_opts.ssl_ca_cert = caPath;
	}

	bind_opts.ssl_cert = certPath;
	bind_opts.ssl_key = keyPath;
	bind_opts.error_string = &err;

	//CLog::debug("mg_bind_opt. port: %s, ca: %s, cert: %s, key: %s\n",
	//	m_sslPort.c_str(), bind_opts.ssl_ca_cert, bind_opts.ssl_cert, bind_opts.ssl_key);

	m_sslConnection = mg_bind_opt(&m_mgr, m_sslPort.c_str(), ev_handler, bind_opts);
	if (!m_sslConnection)
	{
		return false;
	}

	m_sslConnection->user_data = this;
	//mg_enable_multithreading(m_sslConnection);

	mg_set_protocol_http_websocket(m_sslConnection);

	mg_register_http_endpoint(m_sslConnection, "/upload", handle_upload);

	DirEntryMap::iterator it = m_entryMap.begin();
	for (; it != m_entryMap.end(); ++it)
	{
		mg_register_http_endpoint(m_sslConnection, it->first.c_str(), handle_direntry);
	}

	return true;

#else
	return false;
#endif
}

bool CHttpServer::getRemoteAddr(HttpConnection* conn, std::string& name)
{
	char buf[128] = { 0 };
	mg_conn_addr_to_str((mg_connection*)conn, buf, sizeof(buf), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT | MG_SOCK_STRINGIFY_REMOTE);
	name = buf;
	return true;
}

bool CHttpServer::setUploadSubDir(const char* subUrl, const char* dirPath)
{
	if (!subUrl)
	{
		return false;
	}

	if (!dirPath || strlen(dirPath) == 0)
	{
		m_uploadSubDirMap.erase(subUrl);
	}
	else
	{
		m_uploadSubDirMap[subUrl] = dirPath;
	}
	return true;
}

bool CHttpServer::findUploadDir(const std::string& url, std::string& dirpath)
{
	bool got = false;
	PrefixMap::const_iterator it = m_uploadSubDirMap.find(url);
	if (it != m_uploadSubDirMap.end())
	{
		dirpath = it->second;
		got = true;
	}
	return got;
}

void CHttpServer::broadcast(WsDataType type, const char* data, size_t len)
{
	if (!data || len <= 0)
	{
		return;
	}

	AsyncRecord record;
	record.dataType = type;
	record.result.content.assign(data, len);

	putAsyncRecord(record);

}

void CHttpServer::setWebSocket(const char* url, Handler handler, HttpAuth auth)
{
	m_wsUrl = url;
	m_wsHandler = handler;
	m_wsAuth = auth;
}

bool CHttpServer::sendFrame(HttpConnection* conn, WsDataType type, const char* data, size_t len)
{
	if (!data || len <= 0)
	{
		return false;
	}

	AsyncRecord record;
	record.dataType = type;
	record.result.content.assign(data, len);
	record.connection = (mg_connection*)conn;

	return putAsyncRecord(record);
}

void CHttpServer::setVirtualFile(const char* url, Handler handler)
{
	m_vfUrl = url;
	m_vfHandler = handler;
}

void CHttpServer::setTimerTask(int seconds, Task task)
{
	m_timerTask->open(seconds, task);
}